分类
联系方式
  1. 新浪微博
  2. E-mail

Maeiee Weekly No.12:我的博客技术架构

前言

新博客(maxieewong.com)上线已经半年了。从静态生成器到前端界面,全是我从头开发的。再本期中我打算对其 Review 一番,总结一下博客的技术架构,以及背后的思考。

本周在目标设定上有点激进,不断叠加 Flag,导致差点翻车,好在最后给兜住了。

差点翻车的目标

原本想的很简单,因为更换主力机,把静态博客生成器迁移过来,顺带梳理一下。

结果呢?

  1. 不太满意代码实现,重构一下 Python 代码
  2. 本周想学 Rust,那就用 Rust 来重构吧(🧠:你会吗?
    1. 本周完成 80%
  3. 我要 100% 用 Emacs 开发!(VS Code:……

值得一提的是,由于 WSLg 不稳定,Emacs GUI 版本经常会卡死。因此我使用的是命令行版本,经过一个礼拜的开发,不仅 100% Emacs 开发做到了,连鼠标都不需要了……

静态博客生成器目前已经重构完成 80% 了,中秋节应该就能够用上。

博客特色介绍

maxieewong.com 没有使用现成的静态博客生成器。首要原因是我使用 MediaWiki 做知识管理,希望由 MediaWiki 生成博客,这个需求有些小众。第二点是我对博客融入了一些自己的思考,因此独具特色。下面我先介绍下这些特色。

注:博客生成器不开源,原因是我的精力有限,没有精力去维护一个完整的开源项目。同时采用这套知识管理栈的人很少,即便开源,通用性也有限。

基于 MediaWiki 的知识库

我使用 MediaWiki 作为知识管理工具。MediaWiki 是维基百科的基石,支撑了世界上最大的知识库,在功能上也是强大无比的。这也是我选择它的原因。

目前我的 MediaWiki 知识库中包含 2793 篇笔记。本站中的所有文章,也来自于其中,只是其中的一小部分。

静态站点生成器

maxieewong.com 是一个纯静态站点,我写了一个静态博客生成器,能够将 MediaWiki 页面导出,最终生成一个静态站。

静态站没有使用 React/Vue 这些框架,而是使用纯静态的 HTML,能简化就简化。

整个过程如下图所示:

ChangeLog

鲁迅说:文章与其说是写出来的,不如说是改出来的。我深感认同。

文章的第一稿通常比较简陋,需要进行反复修改、打磨。有的优秀作者非常由耐心,能够长期酝酿一篇好文章。而我属于专注力涣散型的,没法长期专注一个事情(这也是为什么我通过 Weekly 强迫自己专注)。

因此我借鉴“迭代开发”的思路,“快速上线,小步快跑”。我会在文章不成熟时就及早发布,随着理解不断加深不断进行完善。

这带来一个问题,用户怎么知道文章更新了呢?

为此我增加了一个 ChangeLog 的实体。对应于博客的首页时间轴。

首页时间轴记录了我对文章的修改历史,就像 Git Commit Log 一般。ChangeLog 的机制也鼓励我不断回顾已有知识,从而帮助我“深入”下去,防止自己浅尝辄止。

注:有一个待改进点,同一天的多次提交其实可以合并。图中是未合并情况,容易造成刷屏。

分类页

像大部分博客一样,maxieewong.com 也有分类功能,通过侧边栏进入。

与众不同的是:分类页自身是一篇文章

分类页是一篇文章的好处是什么?好处是可以帮助我搭建知识体系

注:这是 MediaWiki 的一个特性,每个分类页自身也是一个页面。

举一个具体案例:当我在研究 Flutter 引擎的时候,由于规模过于庞大,容易只见树木不见森林。在 Flutter 的分类页中,我得以回到全局视角,用我自己的话来总结整个 Flutter 技术体系。这会帮助我避免钻牛角尖,从而更好进行整体把握。

VisualEditor

MediaWiki 搭载了一个可视化编辑器,叫做 VisualEditor 。

这个编辑器相当的强大,如下图:

比如图中演示了 VisualEditor 的表格编辑能力,能够轻易地合并单元格,并且单元格内还能支持完整的 wikitext 语法(比如嵌套表格)。这种强大的表格能力,是其它编辑器都难以媲美的。

VisualEditor 非常好用,放在一众云笔记、笔记软件里都是非常出众的。

博客架构

博客的架构图如下:

其中主要分为三层。

最底层首先是 MediaWiki 知识库,里面包含笔记,一部分是私有的,一部分是可导出的。除此之外,还有一个目录保存了博客页面的 HTML 模板。

中间层是 Wiki 的 API,除了 MediaWiki API 外。还使用了 Parsoid 服务,它是 MediaWiki 的一部分,用于将 wikitext 渲染成 HTML。

最上层是我开发的博客生成器,重点将在下面小节中介绍。

博客生成器实现

模块划分

主要分为以下模块:

  1. ChangeLog:维护博客改动历史,并用它生成首页时间线和 RSS Feed
  2. 页面抓取:访问底层服务,获取页面元信息和页面 HTML 快照
    1. 拿到 HTML 之后,需要进行一些 HTML 清洗操作
  3. 页面缓存:将拿到的元信息、HTML、图片保存到本地
  4. 增量生成:因为有 ChangeLog 改动信息,实现了一个增量生成算法

增量生成

因为有 ChangeLog 改动信息,我能够知道有那些内容改变了。

因此在进行生成的时候,不需要重新拉取所有数据,只需要生成较上次之后变化的数据即可,这就是增量生成。

由于是个人博客,我一周也写不出几篇文章,所以每次生成的时候,只需要重新生成这几篇新的或者有修改的。

有了增量算法,博客生成器没有采用多线程技术,包括网页获取、发请求都是单线程阻塞的,大大简化了代码实现。

每次生成的时间都在几秒钟,速度非常快。

以后哪怕有 1k 篇文章,由于是增量生成,还是只会重拉有改动的几篇,所以效率是恒定的。

数据实体

生成器采用的数据实体也比较简单,列举 Rust 版本如下:

// ChangeLog 改动信息
#[derive(Serialize, Deserialize, Debug)]
pub struct RecentChange {
    pub comment: String,
    pub newlen: i32,
    ns: i32,
    old_revid: i32,
    pub oldlen: i32,
    pub pageid: i32,
    rcid: i32,
    revid: i32,
    pub timestamp: String,
    pub title: String,
    #[serde(rename = "type")]
    pub type_field: String
}

#[derive(Serialize, Deserialize, Debug)]
pub struct PageInfo {
    pub pageid: i32,
    pub title: String,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct HomeFeed {
    #[serde(rename = "type")]
    pub type_field: String,
    pub title: String,
    pub timestamp: String,
    pub url: String,
    pub comment: String
}

只有 3 个实体:

  1. RecentChange:标识一条改动,与 MediaWiki API 对齐
  2. PageInfo:页面元信息的简化版本呢
  3. HomeFeed:过滤后的改动,用于生成时间线和 RSS Feed

部署

博客生成器执行完成后,会生成一个完整的静态站点。

我使用 AWS S3 进行静态托管。相关资料有很多,这里不再赘述了。

Rust

通过上述梳理,其实这个博客生成器整体很简单。

但这周自己感觉有点疲惫,主要还是被 Rust 和 Emacs 给带沟里去了。

这一节我先聊聊学习 Rust 一周的感受。

想学一门底层语言

我是一个客户端开发,用 Android 写 Java 起步,后来研究前端 JavaScript 技术栈,再后来搞 Flutter 使用 Dart 技术栈,业余时间里喜欢用 Python 写程序。

随着职业发展的深入,我逐渐从业务层向底层发展,大部分精力在研究 JavaScript Engine、Futter Engine 等框架的底层实现,过去几年里看的最多的代码是 C/C++。

但是我的 C/C++ 属于“哑巴编程”,只会看不会写,顶多只是在底层修修补补。

我意识到,为了再底层更好地走下去,我需要掌握一门底层系统语言。

为啥要学 Rust?

Rust 是近年来热门地底层系统语言,它刚诞生地时候我就有所耳闻,但是直到近年来才变得火爆。

选择就是,C/C++ 还是 Rust?其实两者是各有特色的。我对比的维度如下:

语言 C/C++ Rust
发展 历史悠久 年轻
生态
包管理 复杂 简单
构建系统 复杂 简单
编译器报错

友好度

程序运行

编码缺陷量

语法复杂度 高级高

其中,决定我选择 Rust 的关键是【程序运行编码缺陷量】这一条。

尤其是看了 Flutter/Dart Engine 那十几、几十万行并发 C++ 代码,让我觉得我驾驭不住。

并且 C/C++ 过于复杂,各种宏就像各种咒语。导致不同的项目,尽管都是 C/C++,但是又很不一样,理解起来很吃力。

难学的 Rust

Rust 确实难学,尤其是我平时写的都是 Java/JavaScript/Python/Dart 这种,不用跟指针和内存打交道,而且还有 GC 帮助我们回收资源。

可来到 Rust 后,原来没有了 GC,需要我们自己关注数据是放在堆上还是栈上,关注数据的生命周期,防止出现悬垂指针。

比如用 Python 开发博客生成器,之前写代码都是一遍过的,现在需要跟编译器斗争半天。

不过对我来说,这比 C/C++ 好。C/C++ 里的各种指针、智能指针都需要靠自己去维护,由于复杂度高了以后我驾驭不住,因此会失控,架构会发生混乱。

因此,幸好有 Rust 这个老师在,把一些概念给显化(如生命周期),帮助我意识到自己可能犯的问题。将问题尽早暴露,在早期以低成本解决。

Emacs 开发感受

本周是我第一次 100% 使用 Emacs 进行开发。感受总结来说:

如果不是为了情怀,VS Code 就好。我是纯粹为了情怀,也是为了以后互联网考古,主要是为了玩儿。

如果你是学生,或者刚入门的初学者,我的建议是别浪费太多时间在编辑器上,从 VS Code 入门是一个好选择。不折腾编辑器,先把工作技能修炼到位,挣大钱。

话说回来,在命令行下使用 Emacs,有不一样的体验。因为我会用的命令很少,所以很多操作比较蹩脚,效率上比用 VS Code + 用鼠标要低一点。但是 VS Code + 用鼠标的效率是固定的,而 Emacs 会随着我掌握地加深,效率越来越高,长期回报是可观的。

注:还是需要说明,编码的效率不在编辑器上,就跟写文章的水平不在于写字速度上一样。如果一个复杂的程序不会写,编辑器用的再溜也还是不会写。不要被我带跑偏。

博客未来的架构

博客是我学习系统中的一环,是与我的其它工作流相结合的。

在我的整体系统当中,未来会补齐信息系统,对于笔记系统我再酝酿更换 MediaWiki。

在这些背景之下,博客作为一个微服务,该如何设计呢?

实体完善

现在的的几个实体,RecentChange、HomeFeed 抽象地还是不够好,需要进行更加完善地抽象。

未来除了页面笔记之外,我还考虑打造一个 Info 索引系统,把好的文章、库作为静态站发布出来。

因此,从实体上要有机融合。

更换渲染方式

Parsoid 是 MediaWiki 中的 HTML 快照服务,对我来说整体是一个黑盒,拿到的 HTML 不够纯净。

后续考虑直接拿 wikitext 来自己渲染,比如通过 pandoc。

Lab

我捣鼓的几门技术(Flutter、Rust、JavaScript),都是面向 Web 的。未来会捣鼓一些 Web 小工具什么的。

需要有一个流程化的方式,方便这些 Web 工具的静态发布。

分类

分类是接下来重点关注的内容。

对我来说,每个分类就像一本开源电子书的目录。把纲要写好,能够驱使我体系化地迈进。

下期内容

下周由于临近中秋,事情比较多,就不安排特别的主题了。

把 Rust 博客生成器剩余部分开发完,剩下多读一读文章。

还有就是把老开发机数据备份好。